﻿package djw.tween {
	
	import djw.tween.easing.Quad;
	import flash.events.Event;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getTimer;
	
	/**
	 * A TDTween is the instance of a tween. The TDTween will manipulate the target until the destination properties are reached.
	 */
	public class TDTween {
			
		private var _target				:Object;
		
		private var _init				:Object;
		private var _dest				:Object;
		
		private var _startTime			:Number;
		private var _delay				:Number;
		private var _time				:Number;
		private var _duration			:Number;
		private var _pc					:Number;
		private var _key				:String;
		
		private var _ease				:Function;
		private var _onStart			:Function;
		private var _onStartParams		:Array;
		private var _onUpdate			:Function;
		private var _onUpdateSet		:Boolean;
		private var _onUpdateParams		:Array;
		private var _onComplete			:Function;
		private var _onCompleteParams 	:Array;
		
		private var _completed			:Boolean;
		
		private var _autoVisible		:Boolean;
		private var _autoRemove			:Boolean;
		private var _tracing			:Boolean;
		
		public function TDTween($target:Object=null, $duration:Number=0, $properties:Object=null) {
			if ($target && $properties)
				init($target, $duration, $properties);
		}
		
		public function init($target:Object, $duration:Number, $properties:Object):void {
			_target = $target;
			_duration = $duration;
			_delay = 0;
			_init = { }; 														// initial properties
			_dest = { }; 														// destination properties
			for (_key in $properties) {
				switch(_key) {
					case "ease":												// assign tween internal properties			
					case "onStart":
					case "onUpdate":
					case "onStartParams":
					case "onUpdateParams":
					case "onComplete":
					case "onCompleteParams":
					case "delay": 												
						this["_" + _key] = $properties[_key]; 					
						break;
					case "autoAlpha":											// handle automatic visibility
					case "autoVisible":
						_init.alpha = _target.alpha;
						_dest.alpha = $properties.autoAlpha;
						_autoVisible = true;									
						break;
					case "autoRemove":
						_autoRemove = $properties.autoRemove;
						break;
					case "trace":
						_tracing = $properties.trace;
						break;
					case "scale":												// automatic scaling
						_init.scaleX = _target.scaleX;
						_init.scaleY = _target.scaleY;
						_dest.scaleX = _dest.scaleY = $properties.scale;
						break;
					default: 													// transfer all other poperties to destination
						_init[_key] = _target[_key];
						_dest[_key] = $properties[_key];
				}
			}
			_duration *= 1000;
			_delay *= 1000;			
			_onUpdateSet = _onUpdate != null;
			_startTime = getTimer() + _delay;
			if (_ease == null) _ease = Quad.easeOut; 							// set automatic easing
			if (_autoVisible) _target.visible = true;
			if (_onStart != null) _onStart.apply(null, _onStartParams); 		// call complete if needed;
			if (_duration <= 0) complete();
		}
		
		public function loop($t:int):void {
			_time = $t - _startTime;											// calculate time
			if (_time < 0) return; 												// if time is less than zero, bail
			_pc = _ease(_time, 0, 1, _duration); 								// calculate progress
			for (_key in _dest) 												
				_target[_key] = _dest[_key]*_pc + _init[_key]*(1-_pc);			// applies values
			if (_onUpdateSet) 
				_onUpdate.apply(null, _onUpdateParams);							// calls onUpdate if required
			if (_time >= _duration) complete();									// pushes complete when ready
			if (_tracing) trace(toString());
		}
		
		public function complete():void {
			if (_autoVisible) _target.visible = _target.alpha > 0;				// toggles visibility if needed	
			if (_autoRemove && _target.parent) 									// removes object from stage if requested
				_target.parent.removeChild(_target);
			for (_key in _dest) 
				_target[_key] = _dest[_key];									// corrects end position
			_completed = true;													// declares complete
			if (_onComplete != null)								
				_onComplete.apply(null, _onCompleteParams);						// calls onComplete if required
			TweenDark.kill(_target); 											// removes tween
		}
		
		public function toString():String {
			var str:String = "TDTween: ";
			for (_key in _dest) 												
				str += "\n\t" + _key + ":" + _init[_key] + " > " + _target[_key] + " > " + _dest[_key];
			return str;
		}
		
		/**
		 * The sinner is the target of the tweening.
		 */
		public function get target():Object { return _target; }
		
		/**
		 * The function called when the TDTween is over.
		 */
		public function get onComplete():Function { return _onComplete; }		
		public function set onComplete(value:Function):void {
			_onComplete = value;
		}
		
		/**
		 * The function called on each frame that the tweening occurs.
		 */
		public function get onUpdate():Function { return _onUpdate; }		
		public function set onUpdate(value:Function):void {
			_onUpdate = value;
			_onUpdateSet = _onUpdate == null;
		}
		
		/**
		 * The function called when the tweening commences.
		 */
		public function get onStart():Function { return _onStart; }		
		public function set onStart(value:Function):void {
			_onStart = value;
		}
		
		/**
		 * The ease controls the rate of tweening.
		 */
		public function get ease():Function { return _ease; }		
		public function set ease(value:Function):void {
			_ease = value;
		}
		
		/**
		 * Represents the progress of the tween's manipulation.
		 */
		public function get percent():Number { return _pc; }
		
		/**
		 * Returns true if tween has completed.
		 */
		public function get completed():Boolean { return _completed; }
	}
	
}